package com.anbang.website.utils;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

public class HttpProtocolHandler {

	private static String DEFAULT_CHARSET = "UTF-8";

	/** 连接超时时间，由bean factory设置，缺省为8秒钟 */
	private int defaultConnectionTimeout = 8000;

	/** 回应超时时间, 由bean factory设置，缺省为30秒钟 */
	private int defaultSoTimeout = 30000;

	/** 闲置连接超时时间, 由bean factory设置，缺省为60秒钟 */
	//private int defaultIdleConnTimeout = 60000;

	private int defaultMaxConnPerHost = 30;

	private int defaultMaxTotalConn = 80;

	/** 默认等待HttpConnectionManager返回连接超时（只有在达到最大连接数时起作用）：1秒 */
	//private static final long defaultHttpConnectionManagerTimeout = 3 * 1000;

	/**
	 * HTTP连接管理器，该连接管理器必须是线程安全的.
	 */
	private PoolingHttpClientConnectionManager poolingHttpClientConnectionManager;

	private static HttpProtocolHandler httpProtocolHandler = new HttpProtocolHandler();

	/**
	 * 工厂方法
	 * 
	 * @return
	 */
	public static HttpProtocolHandler getInstance() {
		return httpProtocolHandler;
	}

	/**
	 * 私有的构造方法
	 */
	private HttpProtocolHandler() {
		// 创建一个线程安全的HTTP连接池
		ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
		LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
				.register("http", plainsf)
				.register("https", sslsf)
				.build();
		poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
		// 将最大连接数增加到200
		poolingHttpClientConnectionManager.setMaxTotal(defaultMaxTotalConn);
		// 将每个路由基础的连接增加到20
		poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxConnPerHost);

		// 将目标主机的最大连接数增加到50
		 HttpHost aligatway = new HttpHost(Constant.getServiceUrl(), 80);
		 poolingHttpClientConnectionManager.setMaxPerRoute(new HttpRoute(aligatway), defaultMaxConnPerHost);

	}
	
	public CloseableHttpClient getClient(){
		return HttpClients.custom()
				.setConnectionManager(poolingHttpClientConnectionManager)
				.setRetryHandler(httpRequestRetryHandler)
				.build();
	}

	/**
	 * 执行Http请求
	 * 
	 * @param request
	 *            请求数据
	 * @param strParaFileName
	 *            文件类型的参数名
	 * @param strFilePath
	 *            文件路径
	 * @return
	 * @throws HttpException,
	 *             IOException
	 * @throws URISyntaxException
	 */
	public HttpResponse execute(HttpRequest request, String strParaFileName, String strFilePath) throws HttpException, IOException, URISyntaxException {
		CloseableHttpClient httpClient = HttpClients.custom()
				.setConnectionManager(poolingHttpClientConnectionManager)
				.setRetryHandler(httpRequestRetryHandler)
				.build();

		String charset = request.getCharset();
		charset = charset == null ? DEFAULT_CHARSET : charset;

		CloseableHttpResponse httpResponse = null;
		// get模式且不带上传文件
		if (request.getMethod().equals(HttpRequest.METHOD_GET)) {
			HttpGet method = new HttpGet(request.getUrl());
			List<NameValuePair> nvps = new ArrayList<NameValuePair>();
			NameValuePair[] p = request.getParameters();
			for (int i = 0; i < p.length; i++) {
				nvps.add(p[i]);
			}
			String str = EntityUtils.toString(new UrlEncodedFormEntity(nvps, charset));
			method.setURI(new URI(request.getUrl() +"&"+ str));			
			config(method, request);
			httpResponse = httpClient.execute(method);

		} else if (strParaFileName.equals("") && strFilePath.equals("")) {// post模式且不带上传文件
			HttpPost method = new HttpPost(request.getUrl());
			method.addHeader("Content-Type", "application/x-www-form-urlencoded; text/html; charset=" + charset);
			List<NameValuePair> nvps = new ArrayList<NameValuePair>();
			NameValuePair[] p = request.getParameters();
			for (int i = 0; i < p.length; i++) {
				nvps.add(p[i]);
			}
			method.setEntity(new UrlEncodedFormEntity(nvps, charset));
			config(method, request);
			httpResponse = httpClient.execute(method);
		} else {
			// post模式且带上传文件
			HttpPost method = new HttpPost(request.getUrl());
			MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
			// 设置上传的文件参数
			FileBody binFileBody = new FileBody(new File(strFilePath));
			multipartEntityBuilder.addPart(strParaFileName, binFileBody);
			// 设置上传的其他参数
			ContentType contentType = ContentType.create("text/plain", charset);
			NameValuePair[] p = request.getParameters();
			for (int i = 0; i < p.length; i++) {
				multipartEntityBuilder.addPart(p[i].getName(), new StringBody(p[i].getValue(), contentType));
			}
			HttpEntity reqEntity = multipartEntityBuilder.build();
			method.setEntity(reqEntity);
			config(method, request);
			httpResponse = httpClient.execute(method);
		}

		HttpResponse response = new HttpResponse();
		try {
			HttpEntity entity = httpResponse.getEntity();
			response.setStatusLine(httpResponse.getStatusLine());
			if (request.getResultType().equals(HttpResultType.STRING)) {
				response.setStringResult(EntityUtils.toString(entity, charset));
			} else if (request.getResultType().equals(HttpResultType.BYTES)) {
				response.setByteResult(EntityUtils.toByteArray(entity));
			}
			response.setResponseHeaders(httpResponse.getAllHeaders());
			EntityUtils.consume(entity);
		} finally {
			httpResponse.close();
		}

		return response;
	}

	/**
	 * 将NameValuePairs数组转变为字符串
	 * 
	 * @param nameValues
	 * @return
	 */
	protected String toString(NameValuePair[] nameValues) {
		if (nameValues == null || nameValues.length == 0) {
			return "null";
		}

		StringBuffer buffer = new StringBuffer();

		for (int i = 0; i < nameValues.length; i++) {
			NameValuePair nameValue = nameValues[i];

			if (i == 0) {
				buffer.append(nameValue.getName() + "=" + nameValue.getValue());
			} else {
				buffer.append("&" + nameValue.getName() + "=" + nameValue.getValue());
			}
		}

		return buffer.toString();
	}

	public void config(HttpRequestBase httpRequestBase, HttpRequest request) {
		httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
		httpRequestBase.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		httpRequestBase.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");// "en-US,en;q=0.5");
		httpRequestBase.setHeader("Accept-Charset", "ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");

		 // 设置连接超时
		 int connectionTimeout = defaultConnectionTimeout;
		 if (request.getConnectionTimeout() > 0) {
		 connectionTimeout = request.getConnectionTimeout();
		 }
		
		 // 设置回应超时
		 int soTimeout = defaultSoTimeout;
		 if (request.getTimeout() > 0) {
		 soTimeout = request.getTimeout();
		 }
		
		// 配置请求的超时设置
		RequestConfig requestConfig = RequestConfig.custom()
				.setConnectionRequestTimeout(3000)
				.setConnectTimeout(connectionTimeout)
				.setSocketTimeout(soTimeout)
				.build();
		httpRequestBase.setConfig(requestConfig);
	}

	// 请求重试处理
	HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
		public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
			if (executionCount >= 5) {// 如果已经重试了5次，就放弃
				return false;
			}
			if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
				return true;
			}
			if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
				return false;
			}
			if (exception instanceof InterruptedIOException) {// 超时
				return false;
			}
			if (exception instanceof UnknownHostException) {// 目标服务器不可达
				return false;
			}
			if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
				return false;
			}
			if (exception instanceof SSLException) {// ssl握手异常
				return false;
			}

			HttpClientContext clientContext = HttpClientContext.adapt(context);
			org.apache.http.HttpRequest request = clientContext.getRequest();
			// 如果请求是幂等的，就再次尝试
			if (!(request instanceof HttpEntityEnclosingRequest)) {
				return true;
			}
			return false;
		}
	};
}
